#include "StdAfx.h"
#include "GUIState.h"

using namespace CC3501_Main_GUI;

// Constructor
GUIState::GUIState(void)
{
	this->inputCSVFilePath = nullptr;
	this->upToLine = 1;

	this->calibrated=false;
	this->reachedEndOfFile = false;

	this->inBufferPutterPosition = 0;
	this->inBufferGetterPosition = 0;

	this->outBufferPutterPosition = 0;
	this->outBufferGetterPosition = 0;

	this->calibrationFactorShakerA = 1;
	this->calibrationFactorShakerB = 1;

	this->sentDataRecords = gcnew List<SentDataRecord^>();
	this->receivedDataRecords = gcnew List<ReceivedDataRecord^>();

	this->csvFileAsList = gcnew List<array<String^>^>();
	this->running = false;
	this->calibrationMode = false;
	this->timer = gcnew MyTimer();
	this->MCUTxErrorCount = 0;

	this->nextDataIndex = 0;
}

// Record the sent data record
void GUIState::addSentRecord(SentDataRecord^ inSentDataRecord) {
	this->sentDataRecords->Add(inSentDataRecord);
}

// Record the received data record
void GUIState::addReceivedRecord(ReceivedDataRecord^ inReceivedDataRecord) {
	this->receivedDataRecords->Add(inReceivedDataRecord);
}

// Get the next data index
UInt16 GUIState::getNextDataIndex(void) {
	UInt16 returnValue = this->nextDataIndex;
	
	if (this->nextDataIndex == UInt16::MaxValue) {
		this->nextDataIndex = 0;
	} else {
		this->nextDataIndex += 1;
	}

	return returnValue;
}

// isInCalibrationMode?
Boolean GUIState::isInCalibrationMode(void) {
	return calibrationMode;
}

// isInNormalMode?
Boolean GUIState::isInNormalMode(void) {
	return !calibrationMode;
}

// setCalibrationMode
// When moving to calibration mode, clear the fact that the GUI is calibrated.
void GUIState::setCalibrationMode(void) {
	
	this->calibrationFactorShakerA = 1;
	this->calibrationFactorShakerB = 1;
	this->calibrated = false;

	this->calibrationMode = true;
}

// setNormalMode
void GUIState::setNormalMode(void) {
	this->calibrationMode = false;
}

// Get the timer
MyTimer^ GUIState::getTimer(void) {
	return this->timer;
}

// Check if the timer is currently running
Boolean GUIState::isRunning(void) {
	return this->running;
}

// Stop the timer, and clear all state
void GUIState::stop(void) {
	this->getTimer()->stop();
	this->running = false;
	this->upToLine = 1;
	this->nextDataIndex = 0;

	this->sentDataRecords->Clear();
	this->receivedDataRecords->Clear();
	
	this->inBufferPutterPosition = 0;
	this->inBufferGetterPosition = 0;

	this->outBufferPutterPosition = 0;
	this->outBufferGetterPosition = 0;

	this->reachedEndOfFile = false;
}

void GUIState::writeToStream(StreamWriter^ sw) {	
	sw->WriteLine("CSVData,Index,SentShakerA,SentShakerB,ReceivedShakerA,ReceivedShakerB,ReceivedAccelerometerA,ReceivedAccelerometerB,ReceivedAccelerometerC");
	int j = 0;

	ReceivedDataRecord^ rd = nullptr;

	// Determine the calibration co-efficients
	for(int i = 0; i < this->sentDataRecords->Count; i++) {
		SentDataRecord^ sd = this->sentDataRecords[i];

		if ( rd == nullptr && j < this->receivedDataRecords->Count) {
			rd = this->receivedDataRecords[j];
		}

		// the sent and received data line-up...
		// if they don't line up, we assume the received data is from a future sent data ( as we record everything we send)
		if ( ( rd != nullptr ) && ( sd->getIndex() == rd->getIndex() ) ) {
			
			// Write the line, with all the data
			String^ strToWrite = String::Concat(sd->getCSVData(), ",", sd->getIndex(), ",", sd->getShakerAValue(), ",", sd->getShakerBValue(),",", rd->getShakerAValue(), ",", rd->getShakerBValue(), ",", rd->getAccelerometerAValue(), ",", rd->getAccelerometerBValue(), ",",  rd->getAccelerometerCValue());
			sw->WriteLine(strToWrite);

			j++;
			rd = nullptr;			
		} else {
			String^ strToWrite = String::Concat(sd->getCSVData(), ",", sd->getIndex(), ",", sd->getShakerAValue(), ",", sd->getShakerBValue(),",,,,,");			
			sw->WriteLine(strToWrite);
		}
	}
	
}

void GUIState::performCalibration(void) {
	this->calibrated = true;

	int j = 0;

	ReceivedDataRecord^ rd = nullptr;

	float shakerASum = 0;
	float shakerBSum = 0;
	int shakerACount = 0;
	int shakerBCount = 0;

	// Determine the calibration co-efficients
	for(int i = 0; i < this->sentDataRecords->Count; i++) {
		SentDataRecord^ sd = this->sentDataRecords[i];

		if ( rd == nullptr && j < this->receivedDataRecords->Count) {
			rd = this->receivedDataRecords[j];
		}

		// the sent and received data line-up...
		// if they don't line up, we assume the received data is from a future sent data ( as we record everything we send)
		if ( ( rd != nullptr ) && ( sd->getIndex() == rd->getIndex() ) ) {
	
			// Determine the shaker A co-efficient from the Accelerometer A value
			if ( rd->getAccelerometerAValue() > 0 ) {
				float shakerACo = ( (float) sd->getCSVData() / (float) rd->getAccelerometerAValue() );
				shakerASum += shakerACo;
				shakerACount++;
			}

			// Determine the shaker B co-efficient from the Accelerometer C value
			if ( rd->getAccelerometerCValue() > 0 ) {
				float shakerBCo = ( (float) sd->getCSVData() / (float) rd->getAccelerometerCValue() );
				shakerBSum += shakerBCo;
				shakerBCount++;
			}
			
			j++;
			rd = nullptr;			
		}
	}
	
	if ( shakerACount > 0 ) {
		this->calibrationFactorShakerA = shakerASum / shakerACount;
	}

	if ( shakerBCount > 0 ) {
		this->calibrationFactorShakerB = shakerBSum / shakerBCount;
	}
}

// Resume (start if necessary) timer
void GUIState::resume(void) {
	this->running = true;
	this->getTimer()->resume();
}

// Pause the timer
void GUIState::pause(void) {
	this->running = false;
	this->getTimer()->pause();	
}

// Getter for input CSV file path
System::String^ GUIState::getInputCSVFilePath(void) {
	return this->inputCSVFilePath;
}

// Setter for input CSV file path
void GUIState::setInputCSVFilePath(String^ newInputCSVFilePath) {
	this->inputCSVFilePath = newInputCSVFilePath;
	this->csvFileAsList = this->convertCSVFileToList();
}

// Getter for input CSV file path
List<array<String^>^>^ GUIState::convertCSVFileToList(void) {
	List<array<String^>^>^ myReturnList = gcnew List<array<String^>^>();

	try
	{
		StreamReader^ sr = gcnew StreamReader( this->inputCSVFilePath );

		String^ line;
		while ((line = sr->ReadLine()) != nullptr)
		{
			// Process the csv row
			array<String^>^ csvSplit = line->Split(',');
			// Add the csv row to the return array
			myReturnList->Add(csvSplit);
		}
		sr->Close();
	} catch (Exception^ e)
	{
		// Let the user know what went wrong.
		Debugger::Log(0, nullptr, "The file could not be read:");
		Debugger::Log(0, nullptr, e->Message);
		Debugger::Log(0, nullptr, "\n");
	}

	return myReturnList;
}

// Have we reached the end of the file?
Boolean GUIState::hasReachedEndOfFile(void) {
	if (this->upToLine > this->csvFileAsList->Count) {
		return true;
	} else {
		return false;
	}
}

// If we have exceeded the length of the file, return null (nullptr)
// else, return the row we're up to
array<String^>^ GUIState::readNextLine(void) {

	if(this->upToLine > this->csvFileAsList->Count) {
		this->reachedEndOfFile = true;
		return nullptr;
	} else {
		array<String^>^ csvRow = this->csvFileAsList[this->upToLine - 1];
		this->upToLine += 1;
		return csvRow;
	}
}

// Get the lines of the CSV
List<array<String^>^>^ GUIState::readAllLines(void) {
	return this->csvFileAsList;
}

// Get the list of sent data
List<SentDataRecord^>^ GUIState::getSentDataRecords(void) {
	return this->sentDataRecords;
}

// Get the list of received data
List<ReceivedDataRecord^>^ GUIState::getReceivedDataRecords(void) {
	return this->receivedDataRecords;
}

// Read the first column of the next row as a byte
Byte^ GUIState::readFirstColumnOfNextRowAsByte(void) {

	array<String^>^ csvRow = this->readNextLine();

	if (csvRow == nullptr) {
		// TODO: Throw an exception
		return nullptr;
	} else {
		String^ elem = csvRow[0];
		int elem_i = System::Convert::ToUInt16(elem);

		return System::Convert::ToByte(elem_i);
	}
}

// Get the calibration factor for shaker A
float GUIState::getCalibrationFactorShakerA(void) {
	return this->calibrationFactorShakerA;
}

// Get the calibration factor for shaker B
float GUIState::getCalibrationFactorShakerB(void) {
	return this->calibrationFactorShakerB;
}

// Read the first column of the next row as a UInt16
UInt16^ GUIState::readFirstColumnOfNextRowAsUInt16(void) {
	
	array<String^>^ csvRow = this->readNextLine();

	if (csvRow == nullptr) {
		// TODO: Throw an exception
		return nullptr;
	} else {
		String^ elem = csvRow[0];
		NumberStyles style = NumberStyles::None;
		UInt16^ elem_i = UInt16::Parse(elem, style);
		
		return elem_i;
	}
}

// Set the line position of the CSV file to *val*
void GUIState::setCSVLinePosition(int val) {
	this->upToLine = val;
}

// Reset the line position of the CSV file
// equivalent to `setLinePosition(1)`
void GUIState::resetCSVLinePosition(void) {
	this->setCSVLinePosition(1);
}

// Get In Buffer Putter Position
int GUIState::getInBufferPutterPosition(void) {
	return this->inBufferPutterPosition;
}

// Get In Buffer Getter Position
int GUIState::getInBufferGetterPosition(void) {
	return this->inBufferGetterPosition;
}

// Get In Buffer Space
int GUIState::getInBufferSpace(void) {
	if (this->inBufferGetterPosition > this->inBufferPutterPosition) {
		return (this->inBufferGetterPosition - this->inBufferPutterPosition);
	} else {
		return (GUIState::inBufferSize - ( this->inBufferPutterPosition - this->inBufferGetterPosition) );
	}
}

int GUIState::getInBufferUsage(void) {
	return (GUIState::inBufferSize - this->getInBufferSpace());
}

// Get Out Buffer Putter Position
int GUIState::getOutBufferPutterPosition(void) {
	return this->outBufferPutterPosition;
}

// Get Out Buffer Getter Position
int GUIState::getOutBufferGetterPosition(void) {
	return this->outBufferGetterPosition;
}

// Get Out Buffer Space
int GUIState::getOutBufferSpace(void) {
	if (this->outBufferGetterPosition > this->outBufferPutterPosition) {
		return (this->outBufferGetterPosition - this->outBufferPutterPosition);
	} else {
		return (GUIState::outBufferSize - ( this->outBufferPutterPosition - this->outBufferGetterPosition) );
	}
}

// Get Out Buffer Usage
int GUIState::getOutBufferUsage(void) {
	return GUIState::outBufferSize - this->getOutBufferSpace();
}

// Error Flags
Boolean GUIState::isMCUInErrorState(void) {
	Boolean error = false;

	if ( this->getMCUTxErrorCount() > 0 ) {
		error = true;
	}

	return error;
}

// Get the MCU Tx Error Count
int GUIState::getMCUTxErrorCount(void) {
	return this->MCUTxErrorCount;
}
		
// is the GUI calibrated?
Boolean GUIState::isCalibrated(void) {
	return this->calibrated;
}

// Set the In Buffer Putter Position
void GUIState::setInBufferPutterPosition(int newPos) {
	this->inBufferPutterPosition = newPos;
}

// Set the In Buffer Getter Position
void GUIState::setInBufferGetterPosition(int newPos) {
	this->inBufferGetterPosition = newPos;
}

// Set the Out Buffer Putter Position
void GUIState::setOutBufferPutterPosition(int newPos) {
	this->outBufferPutterPosition = newPos;
}

// Set the Out Buffer Getter Position
void GUIState::setOutBufferGetterPosition(int newPos) {
	this->outBufferGetterPosition = newPos;
}

// Set the MCU Transfer Error Count
void GUIState::setMCUTxErrorCount(int newErrCnt) {
	this->MCUTxErrorCount = newErrCnt;
}

// Increase the In Buffer Putter Position
void GUIState::increaseInBufferPutterPosition(int incr) {
	this->inBufferPutterPosition  = ( ( this->inBufferPutterPosition + incr )  % GUIState::inBufferSize );
}

// Increase the Out Buffer Getter Position
void GUIState::increaseOutBufferGetterPosition(int incr) {
	this->outBufferGetterPosition = ( ( this->outBufferGetterPosition + incr ) % GUIState::outBufferSize );
}